Skip to content

feat(terminal): integrated terminal improvements and workspace panel enhancements#2717

Merged
abose merged 36 commits intomainfrom
mac
Mar 2, 2026
Merged

feat(terminal): integrated terminal improvements and workspace panel enhancements#2717
abose merged 36 commits intomainfrom
mac

Conversation

@abose
Copy link
Member

@abose abose commented Mar 1, 2026

Description:

Summary

  • Add View > Terminal menu entry, F4 shortcut, and auto-focus on panel shown
image
  • Add Shift+Escape to toggle focus between editor and bottom panel; opens Quick
    Access if no panel is visible
  • Add right-click context menu in terminal with Copy, Paste, and Clear
image
  • Add "Open in Integrated Terminal" to project file tree context menu
image
  • Add Quick Access (app-drawer) toolbar button and tab icon
  • Add requestClose API with onCloseRequested handler for panels, used for
    terminal close confirmation dialogs
  • Extract inline toast into reusable NotificationUI.showToastOn API; show
    shift+escape hint on first terminal open
  • Fix plugin panel width clamping on window resize (enforce minimum width including
    icons bar)
  • Fix terminal title handling: use stale-title flag instead of overwriting, reset
    on child process exit
  • Fix test reliability: terminate PhNode on reload, fix FileFilters race condition,
    fix codehints test, fix terminal test timing

Test plan

  • Terminal opens via View > Terminal menu and F4 shortcut
  • Shift+Escape toggles focus between editor and active bottom panel
  • Shift+Escape opens Quick Access panel when no panel is visible
  • Right-click in terminal shows context menu with Copy, Paste, Clear
  • "Open in Integrated Terminal" appears in project tree context menu
  • App-drawer button in toolbar toggles Quick Access panel
  • Terminal close confirmation appears when closing panel with active sessions
  • Plugin panel respects minimum width on window resize
  • All 211 mainview integration tests pass
  • All terminal integration tests pass
  • FileFilters integration tests pass without race conditions

abose added 30 commits February 26, 2026 21:41
…ling

- Add VIEW_TERMINAL command to Commands.js and View menu
- Remove toolbar-terminal sidebar icon and related CSS
- Terminal command shows/focuses panel, cycles through terminals when
  panel is visible and focused with 2+ terminals open
- Extract _togglePanels() in WorkspaceManager for reuse by both
  Escape key and status bar chevron
- Escape toggles bottom panel, Shift+Escape cycles open panel tabs
- Add PanelView.showNextPanel() for cycling open bottom panels
- Fix DefaultPanelView terminal card to use Commands.VIEW_TERMINAL
- Add Panel.focus() API (default returns false, override for focusable panels)
- Add PanelView.getActiveBottomPanel() to retrieve the active panel
- Terminal panel overrides focus() to focus the active terminal instance
- Terminal passes Shift+Escape through to WorkspaceManager
- Shift+Escape from editor focuses the active bottom panel if visible
- Shift+Escape from anywhere else focuses the active editor
- Add F4 keyboard shortcut for view.terminal command
- Listen for EVENT_PANEL_SHOWN to focus terminal when panel becomes visible
…open

- Add one-time toast notification showing Shift+Esc shortcut hint
- Internationalize toast text via TERMINAL_FOCUS_HINT string
- Add theme-aware toast styles for light and dark themes
…onUI.showToastOn API

Move terminal focus hint toast implementation into NotificationUI as a
generic showToastOn(container, template, options) function. Move toast
CSS from Extn-Terminal.less to brackets.less as .inline-toast. Add unit
tests for the new API.
Plugin panels could encroach into the sidebar/content area when the
window was resized smaller, or reopen too wide after a resize while
closed. Add _clampPluginPanelWidth to enforce max width limits both
during window resize events and when panels are first shown.
Add registerOnCloseRequestedHandler/requestClose API to both PanelView
and PluginPanelView. The tab close button now calls requestClose() which
invokes the async handler before hiding. Terminal registers a handler
that confirms before disposing all terminals when active processes are
running or multiple terminals are open.
…fic messages

Use distinct dialog titles, messages, and button labels for three
scenarios: single terminal with active process, multiple idle terminals,
and multiple terminals with active processes. Singular/plural forms use
separate i18n keys for proper translation support.
Tests cover: single idle terminal closes without dialog, multiple
terminals show close-all dialog, active process shows close-terminal
dialog, stop-processes dialog variant, cancel preserves state, confirm
disposes all terminals, and programmatic hide() keeps terminals alive.
Also adds _writeToActiveTerminal test helper to terminal extension.
Use condition-based polling instead of fixed timeouts to prevent flaky
tests. Add waitForShellReady() and waitForActiveProcess() helpers that
trigger flyout process refresh and poll the flyout title text.
Add a beforeunload handler in SpecRunnerUtils that calls
terminateNode() on the test iframe's Node engine when the test
runner page reloads. This prevents orphaned phnode.exe processes
from holding directory locks on Windows, which caused EBUSY errors
in consecutive integration test runs.

Also simplifies Terminal-integ-test afterAll by removing the
Windows-specific 5s delay that was a workaround for the same issue.
The beforeunload handler only terminated the test iframe's PhNode but
not the SpecRunner's own process, leaving orphaned phnode processes
after every test runner reload.
When a child process (e.g. claude) sets a custom terminal title via
escape sequences and then exits, shells like zsh on macOS do not emit
a title reset. The flyout tab was stuck showing the old title.

Reset inst.title to the shell profile name when the foreground process
returns to the shell. Also fix _isShellProcess to handle login-shell
prefixes like "-zsh", and skip the cwd-in-title check on macOS where
zsh does not set it.
The previous approach (b51de0b) directly overwrote inst.title when
the foreground process returned to the shell, which broke Linux where
bash sets the title via PS1 escape sequences on each prompt. Replace
with a _titleStale flag that is set on non-shell→shell transitions
and cleared when onTitleChange fires, so shells that emit title
sequences (bash/Linux) work correctly while shells that don't (zsh/Mac)
still show the profile name instead of stale child titles.

Also fix the Linux integration test to trigger a prompt refresh
(echo + enter) after shell init so PS1 title escapes fire reliably.
Add a grid icon above the profile button in the right toolbar that
toggles the Quick Access (default) panel. The button shows a
selected/pressed state when the panel is open and deselects when the
panel is hidden or another panel opens.

Also remove unused $pluginIconsBar variable in MainViewManager tests
and add 5 integration tests for the new button behavior.
…large SVG

Add the app-drawer grid icon to the Quick Access panel tab title for
visual consistency with the toolbar button. Enlarge the SVG grid
squares for better visibility at small sizes. Rename panel title
from "Quick Access" to "Tools".
Register terminal-specific commands and a context menu on the terminal
content area. Copy is disabled when there's no selection. All actions
refocus the terminal after executing.
…menu

Adds an "Integrated Terminal" option to the "Open in" submenu in both
the project tree and working set context menus. Opens a new terminal
at the selected file's directory (or folder itself if a directory is
selected). Refactors cwd resolution into a reusable _toNativePath helper.
…text menu

Test clear command removes buffer content, copy command reads the xterm
selection, paste command writes mocked clipboard text to the PTY, and
beforeContextMenuOpen disables Copy when nothing is selected. Guard
test-only exports behind Phoenix.isTestWindow.
The "clear stale title after child process exits" test had a race
condition on Windows where the flyout label still showed "node.exe"
because the async process detection hadn't completed yet. Add an
awaitsFor that polls until the flyout label no longer contains "node".
Set exclusion filter before opening the search bar to prevent a race
where _showFindBar() inherits the previous query and triggers an
unfiltered search that populates the worker cache with all files.
Also add retry loops to exclude-filter tests to handle instant/deferred
search races, matching the existing pattern in "should search in files".
… min width

- Update tests for new escape toggle behavior (second escape re-shows panel)
- Replace shift-escape collapse tests with focus-switching tests
- Add tests for: shift-escape focus toggle, custom panel focus handler,
  default panel open on shift-escape, app-drawer-button toggle
- Fix _showPluginSidePanel to include icons bar width in makeResizable
  minSize so toolbar respects minimum content + icons width
- Fix _clampPluginPanelWidth to enforce minimum width (not just maximum)
- Move #app-drawer-button from runtime JS creation to index.html
- Move app-drawer-button inline styles to brackets.less
The codehints visibility test used test.js which only contains a comment,
so no code hints were available at position (0,0). Changed to test.html
with cursor at (8,1) inside a tag name where HTML hints reliably appear.
Also removed debug logging and added focus assertion to custom handler test.
Suppress the automatic PTY resize during fitAddon.fit() and clear only
the prompt line to avoid duplicate/garbled output caused by readline
redrawing the prompt on top of xterm's reflowed buffer. Add
ResizeObserver for reliable resize detection and skip redundant fits
when dimensions haven't changed.
Clear the prompt region before xterm.js reflow to prevent ghost lines
caused by reflowCursorLine:false (xterm default) which excludes the
cursor row from reflow. When widening, wrapped prompt lines cannot merge
back, leaving stale fragments visible.

The fix walks up through isWrapped lines to find the prompt start, erases
that region, then calls fitAddon.fit() inside the write() callback to
ensure the erase is applied before reflow. Readline's SIGWINCH redraw
then writes a clean prompt at the new width.

Also removes the ResizeObserver (redundant with WorkspaceManager events),
removes the PTY resize suppression mechanism, and increases the resize
debounce to 300ms to avoid intermediate SIGWINCH during drag-resizing.
…outs

Terminal test: replace unreliable PS1-based terminal title check with
direct `pwd` buffer output check. The title check depended on the shell
having PS1 escape sequences that set the terminal title, which varies
across CI environments.

PreferencesManager test: increase awaitsFor timeouts from the default
2s to 10s in _verifySinglePreference, since loading projects and
applying preferences can be slow in CI.
abose added 6 commits March 2, 2026 11:32
The awaitsFor loop only checked that CSS results were absent but did not
wait for HTML results to be populated. In Firefox on CI the search can
remove CSS matches before finishing HTML indexing, causing the subsequent
expect to see undefined. Add the positive HTML check to both awaitsFor
conditions, matching the pattern already used in the filter-switching test.
Await _createNewTerminal() in _showTerminal() so the VIEW_TERMINAL
command completes only after PTY spawn finishes, eliminating a race
where waitForShellReady() started its timer before the shell was
spawned. Also add a fail-fast isAlive check in waitForShellReady()
and an afterEach cleanup for leftover dialogs.
…ation

The ghost-line resize fix in _fit() unconditionally erased the prompt
region before reflowing. This caused two bugs:

1. Tab switching: show() called _fit() which erased the prompt, but
   dimensions hadn't changed so fitAddon.fit() was a no-op — no
   SIGWINCH fired and the shell never redrew the prompt.

2. New terminal (panel hidden): spawn() used proposeDimensions() to
   create the PTY at the actual container size while xterm was still
   at default 80×24. The later _fit() saw a dimension mismatch, erased
   the prompt, and resized xterm — but the PTY was already at that size
   so SIGWINCH was a no-op.

Fix: only erase the prompt region when dimensions are actually changing,
and call fitAddon.fit() before spawn() so xterm and PTY start at the
same size.
Workaround for node-pty #850: the prebuilt spawn-helper binary ships
without execute permissions in the npm tarball, causing PTY spawns to
fail with EACCES on macOS CI. This postinstall script ensures +x is set
after npm install. Remove once node-pty >=1.2.0 is available.
The xterm canvas has fixed pixel dimensions set by fitAddon.fit().
During drag-resize the terminal content area shrinks but the canvas
retains its old size, visually overflowing into the live preview.
Add overflow: hidden to .terminal-content-area so the canvas is
clipped instantly while the debounced fit adjusts it after the drag.
The MCP reload handlers called location.reload() without dismantling
the Tauri trust ring first. This left stale keys in place, preventing
the reloaded page from establishing a new trust ring.

trust_ring.js now passes its reference to phoenix-builder via a
write-once setKernalModeTrust setter. A shared _dismantleTrustRing
helper awaits dismantleKeyring() with a 5s timeout before reloading.
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 2, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
7.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@abose abose merged commit f9643f4 into main Mar 2, 2026
16 of 21 checks passed
@abose abose deleted the mac branch March 2, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant